package com.jz.dsq;

import com.jz.dsq.specification.*;
import org.springframework.data.jpa.domain.Specification;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;

import static javax.persistence.criteria.Predicate.BooleanOperator.OR;

/**
 * @author zhangliang
 */
public class PredicateBuilder<T> {

    private final Predicate.BooleanOperator operator;

    private final List<Specification<T>> specifications;

    public PredicateBuilder(Predicate.BooleanOperator operator) {
        this.operator = operator;
        this.specifications = new ArrayList<>();
    }

    public PredicateBuilder<T> eq(String property, Object... values) {
        return this.predicate(new EqualSpecification<T>(property, values));
    }
    public PredicateBuilder<T> ne(String property, Object... values) {
        return this.predicate(new NotEqualSpecification<T>(property, values));
    }
    public PredicateBuilder<T> gt(String property, Comparable<?> compare) {
        return this.predicate(new GtSpecification<T>(property, compare));
    }
    public PredicateBuilder<T> ge(String property, Comparable<? extends Object> compare) {
        return this.predicate(new GeSpecification<T>(property, compare));
    }

    public PredicateBuilder<T> lt(String property, Comparable<?> compare) {
        return this.predicate(new LtSpecification<T>(property, compare));
    }
    public PredicateBuilder<T> le(String property, Comparable<?> compare) {
        return this.predicate(new LeSpecification<T>(property, compare));
    }
    public PredicateBuilder<T> between(String property, Object lower, Object upper) {
        return this.predicate(new BetweenSpecification<T>(property, lower, upper));
    }
    public PredicateBuilder<T> like(String property, String... patterns) {
        return this.predicate(new LikeSpecification<T>(property, patterns));
    }
    public PredicateBuilder<T> notLike(String property, String... patterns) {
        return this.predicate(new NotLikeSpecification<T>(property, patterns));
    }
    public PredicateBuilder<T> in(String property, Collection<?> values) {
        return this.predicate(new InSpecification<T>(property, values));
    }
    public PredicateBuilder<T> notIn(String property, Collection<?> values) {
        return this.predicate(new NotInSpecification<T>(property, values));
    }

    public PredicateBuilder<T> predicate(Specification<T> specification) {
        this.specifications.add(specification);
        return this;
    }

    public Specification<T> build() {
        return (Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb) -> {
            Predicate[] predicates = new Predicate[specifications.size()];
            for (int i = 0; i < specifications.size(); i++) {
                predicates[i] = specifications.get(i).toPredicate(root, query, cb);
            }
            if (Objects.equals(predicates.length, 0)) {
                return null;
            }
            return OR.equals(operator) ? cb.or(predicates) : cb.and(predicates);
        };
    }
}
